﻿#include "global_funcs.h"
#include <QFile>
#include <QDebug>
#include <QApplication>
#include <QDomDocument>
#include <QMetaType>
#include <QMetaMethod>
#include <QLibrary>

#define MODULE_CONFIG_FILE_NAME "ModuleConfig.xml"

bool enumModule(QStringList &moduleNames)
{
    QFile xmlFile(qApp->applicationDirPath() + "/" + MODULE_CONFIG_FILE_NAME);
    if (!xmlFile.exists()) {
        qCritical() << "Module config file not found!";
        return false;
    }

    QDomDocument doc;
    if (!doc.setContent(&xmlFile)) {
        qCritical() << "Module config file parse failed!";
        return false;
    }

    QDomElement rootElem = doc.documentElement();
    QDomNodeList moduleNodes = rootElem.childNodes();
    for (int i = 0; i < moduleNodes.size(); i++)
        moduleNames.append(moduleNodes.at(i).toElement().attribute("name"));

    return true;
}

static bool getModuleInfo(const QString &moduleName, QString &libPath, QString &className)
{
    QFile xmlFile(qApp->applicationDirPath() + "/" + MODULE_CONFIG_FILE_NAME);
    if (!xmlFile.exists()) {
        qCritical() << "Module config file not found!";
        return false;
    }

    QDomDocument doc;
    if (!doc.setContent(&xmlFile)) {
        qCritical() << "Module config file parse failed!";
        return false;
    }

    QDomElement rootElem = doc.documentElement();
    QDomNodeList moduleNodes = rootElem.childNodes();
    for (int i = 0; i < moduleNodes.size(); i++) {
        QDomElement moduleElem = moduleNodes.at(i).toElement();
        if (moduleName == moduleElem.attribute("name")) {
            QDomElement pathElem = moduleElem.firstChildElement("path");
            if (pathElem.isNull()) {
                qCritical() << "pathElem not found!";
                return false;
            }

            QDomElement classElem = moduleElem.firstChildElement("class");
            if (classElem.isNull()) {
                qCritical() << "classElem not found!";
                return false;
            }

            libPath = pathElem.text();
            className = classElem.text();
            return true;
        }
    }

    return false;
}

static bool checkIfAllVariant(const QList<QByteArray>& parameterTypes)
{
    foreach (QByteArray parameterType, parameterTypes) {
        if (parameterType != "QVariant&")
            return false;
    }

    return true;
}

bool enumMethod(const QString& moduleName, QStringList& methodSignatures)
{
    QString libPath, className;
    if (!getModuleInfo(moduleName, libPath, className)) {
        qCritical() << "getModuleInfo failed!";
        return false;
    }

    QLibrary lib(qApp->applicationDirPath() + "/" + libPath);
    if (!lib.load()) {
        qCritical() << "Module [" << qApp->applicationDirPath() + "/" + libPath << "] load failed with error: " << lib.errorString();
        return false;
    }

    int metaType = QMetaType::type(QString(className).append("*").toLatin1());
    qDebug() << "metaType=" << metaType;
    if (QMetaType::UnknownType != metaType) {
        const QMetaObject *metaObj = QMetaType::metaObjectForType(metaType);
        int methodCount = metaObj->methodCount();
        for (int i = 0; i < methodCount; i++) {
            QMetaMethod aMethod = metaObj->method(i);
            if (aMethod.methodType() == QMetaMethod::Slot &&
                    aMethod.access() == QMetaMethod::Public &&
                    aMethod.isValid() &&
                    aMethod.returnType() == QMetaType::Int &&
                    aMethod.parameterCount() <= 10 &&
                    checkIfAllVariant(aMethod.parameterTypes())) {
                methodSignatures.append(aMethod.methodSignature());
            }
        }

        return true;
    }

    qCritical() << "Unknown metaType!";
    return false;
}

bool enumParam(const QString &moduleName, const QString &methodSignature, QList<QByteArray>& names)
{
    QString libPath, className;
    if (!getModuleInfo(moduleName, libPath, className)) {
        qCritical() << "getModuleInfo failed!";
        return false;
    }

    QLibrary lib(qApp->applicationDirPath() + "/" + libPath);
    if (!lib.load()) {
        qCritical() << "Module [" << libPath << "] load failed!";
        return false;
    }

    int metaType = QMetaType::type(QString(className).append("*").toLatin1());
    qDebug() << "metaType=" << metaType;
    if (QMetaType::UnknownType != metaType) {
        const QMetaObject *metaObj = QMetaType::metaObjectForType(metaType);
        QByteArray normalizedSignature = QMetaObject::normalizedSignature(methodSignature.toLatin1());
        int index = metaObj->indexOfMethod(normalizedSignature.data());
        if (index != -1) {
            QMetaMethod aMethod = metaObj->method(index);
            names = aMethod.parameterNames();
            return true;
        }
    }

    qCritical() << "Unknown metaType!";
    return false;
}

bool invokeMethod(QObject* testObj, const QString &methodName, QVariantList& params, int& retVal)
{
    QByteArray className = QString(testObj->metaObject()->className()).append("*").toLatin1();
    int metaType = QMetaType::type(className);
    qDebug() << "metaType=" << metaType;
    if (QMetaType::UnknownType != metaType) {
        const QMetaObject *metaObj = QMetaType::metaObjectForType(metaType);
        bool ret;
        switch (params.count()) {
        case 0:
            ret = metaObj->invokeMethod(testObj, methodName.toLatin1(),
                                        Qt::DirectConnection,
                                        Q_RETURN_ARG(int, retVal));
            break;
        case 1:
            ret = metaObj->invokeMethod(testObj, methodName.toLatin1(),
                                        Qt::DirectConnection,
                                        Q_RETURN_ARG(int, retVal),
                                        Q_ARG(QVariant&, params[0]));
            break;
        case 2:
            ret = metaObj->invokeMethod(testObj, methodName.toLatin1(),
                                        Qt::DirectConnection,
                                        Q_RETURN_ARG(int, retVal),
                                        Q_ARG(QVariant&, params[0]),
                                        Q_ARG(QVariant&, params[1]));
            break;
        case 3:
            ret = metaObj->invokeMethod(testObj, methodName.toLatin1(),
                                        Qt::DirectConnection,
                                        Q_RETURN_ARG(int, retVal),
                                        Q_ARG(QVariant&, params[0]),
                                        Q_ARG(QVariant&, params[1]),
                                        Q_ARG(QVariant&, params[2]));
            break;
        case 4:
            ret = metaObj->invokeMethod(testObj, methodName.toLatin1(),
                                        Qt::DirectConnection,
                                        Q_RETURN_ARG(int, retVal),
                                        Q_ARG(QVariant&, params[0]),
                                        Q_ARG(QVariant&, params[1]),
                                        Q_ARG(QVariant&, params[2]),
                                        Q_ARG(QVariant&, params[3]));
            break;
        case 5:
            ret = metaObj->invokeMethod(testObj, methodName.toLatin1(),
                                        Qt::DirectConnection,
                                        Q_RETURN_ARG(int, retVal),
                                        Q_ARG(QVariant&, params[0]),
                                        Q_ARG(QVariant&, params[1]),
                                        Q_ARG(QVariant&, params[2]),
                                        Q_ARG(QVariant&, params[3]),
                                        Q_ARG(QVariant&, params[4]));
            break;
        case 6:
            ret = metaObj->invokeMethod(testObj, methodName.toLatin1(),
                                        Qt::DirectConnection,
                                        Q_RETURN_ARG(int, retVal),
                                        Q_ARG(QVariant&, params[0]),
                                        Q_ARG(QVariant&, params[1]),
                                        Q_ARG(QVariant&, params[2]),
                                        Q_ARG(QVariant&, params[3]),
                                        Q_ARG(QVariant&, params[4]),
                                        Q_ARG(QVariant&, params[5]));
            break;
        case 7:
            ret = metaObj->invokeMethod(testObj, methodName.toLatin1(),
                                        Qt::DirectConnection,
                                        Q_RETURN_ARG(int, retVal),
                                        Q_ARG(QVariant&, params[0]),
                                        Q_ARG(QVariant&, params[1]),
                                        Q_ARG(QVariant&, params[2]),
                                        Q_ARG(QVariant&, params[3]),
                                        Q_ARG(QVariant&, params[4]),
                                        Q_ARG(QVariant&, params[5]),
                                        Q_ARG(QVariant&, params[6]));
            break;
        case 8:
            ret = metaObj->invokeMethod(testObj, methodName.toLatin1(),
                                        Qt::DirectConnection,
                                        Q_RETURN_ARG(int, retVal),
                                        Q_ARG(QVariant&, params[0]),
                                        Q_ARG(QVariant&, params[1]),
                                        Q_ARG(QVariant&, params[2]),
                                        Q_ARG(QVariant&, params[3]),
                                        Q_ARG(QVariant&, params[4]),
                                        Q_ARG(QVariant&, params[5]),
                                        Q_ARG(QVariant&, params[6]),
                                        Q_ARG(QVariant&, params[7]));
            break;
        case 9:
            ret = metaObj->invokeMethod(testObj, methodName.toLatin1(),
                                        Qt::DirectConnection,
                                        Q_RETURN_ARG(int, retVal),
                                        Q_ARG(QVariant&, params[0]),
                                        Q_ARG(QVariant&, params[1]),
                                        Q_ARG(QVariant&, params[2]),
                                        Q_ARG(QVariant&, params[3]),
                                        Q_ARG(QVariant&, params[4]),
                                        Q_ARG(QVariant&, params[5]),
                                        Q_ARG(QVariant&, params[6]),
                                        Q_ARG(QVariant&, params[7]),
                                        Q_ARG(QVariant&, params[8]));
            break;
        case 10:
            ret = metaObj->invokeMethod(testObj, methodName.toLatin1(),
                                        Qt::DirectConnection,
                                        Q_RETURN_ARG(int, retVal),
                                        Q_ARG(QVariant&, params[0]),
                                        Q_ARG(QVariant&, params[1]),
                                        Q_ARG(QVariant&, params[2]),
                                        Q_ARG(QVariant&, params[3]),
                                        Q_ARG(QVariant&, params[4]),
                                        Q_ARG(QVariant&, params[5]),
                                        Q_ARG(QVariant&, params[6]),
                                        Q_ARG(QVariant&, params[7]),
                                        Q_ARG(QVariant&, params[8]),
                                        Q_ARG(QVariant&, params[9]));
            break;
        default:
            qCritical() << "Unsupport params num!";
            ret = false;
        }

        return ret;
    }

    qCritical() << "Unknown metaType!";
    return false;
}

bool createTestObj(const QString &moduleName, QObject **pTestObj)
{
    QString libPath, className;
    if (!getModuleInfo(moduleName, libPath, className)) {
        qCritical() << "getModuleInfo failed!";
        return false;
    }

    QLibrary lib(qApp->applicationDirPath() + "/" + libPath);
    if (!lib.load()) {
        qCritical() << "Module [" << libPath << "] load failed!";
        return false;
    }

    int metaType = QMetaType::type(className.append("*").toLatin1());
    if (QMetaType::UnknownType != metaType) {
        const QMetaObject *metaObj = QMetaType::metaObjectForType(metaType);
        *pTestObj = metaObj->newInstance();
        return true;
    }
    else {
        qCritical() << "Cannot find specified class!";
        *pTestObj = nullptr;
        return false;
    }
}
